//
// Created by mouse on 2019/11/12.
//

#include "app_mqtt.h"

static LinkedList<RecvCallback *> _recvHandlerCallbacks = LinkedList<RecvCallback *>(
        [](RecvCallback *recvCallback) { delete recvCallback; });

static String reportDeviceTopic;
static String synchronizeDeviceTopic;

void NetworkTask::messageReceived(String &topic, String &payload)
{
    LOG_DEBUG("Recv:%s -> %s\r\n", topic.c_str(), payload.c_str());
    for (uint32_t i = 0; i < _recvHandlerCallbacks.length(); ++i)
    {
        if (topic.equals((*_recvHandlerCallbacks.nth(i))->topic))
        {
            (*_recvHandlerCallbacks.nth(i))->callback(topic, payload);
        }
    }
}

NetworkTask::NetworkTask()
{
    wiFiClient = new WiFiClient();

    mqttClient = new MQTTClient(SEND_BUFFER_SIZE);


    if (!String(configInfo->device_key).isEmpty())
    {
        MqttUtils::TopicEntity *topic = new MqttUtils::TopicEntity();

        strcpy(topic->title, MQTT_TOPIC_SYS);
        strcpy(topic->product, "*");
        strcpy(topic->device, configInfo->device_key);
        strcpy(topic->function, "post");
        strcpy(topic->item, "deviceInfo");
        reportDeviceTopic = MqttUtils::toTopicString(topic);

        strcpy(topic->title, MQTT_TOPIC_SYS);
        strcpy(topic->product, "*");
        strcpy(topic->device, configInfo->device_key);
        strcpy(topic->function, "set");
        strcpy(topic->item, "deviceInfo");
        synchronizeDeviceTopic = MqttUtils::toTopicString(topic);

        delete topic;
    }
}

MQTTClient *NetworkTask::getMqttClient()
{
    return mqttClient;
}

void NetworkTask::mqttConnectSync()
{
    LOG_INFO("connecting to %s ...\r\n", configInfo->mqtt_server);
    char id[32];
    sprintf(id, "nodemcu-%s", String(ESP.getChipId()).c_str());
    while (!mqttClient->connect(id, configInfo->mqtt_username, configInfo->mqtt_password))
    {
        LOG_INFO(".");
        delay(2000);
    }
    LOG_INFO("mqtt connection successed\r\n");
}

void NetworkTask::setWill()
{
    if (reportDeviceTopic.isEmpty())
    {
        LOG_DEBUG("device key is empty\r\n");
        return;
    }

    String output;

    int productKeysSize = configInfo->product_key_size;

    const size_t capacity = JSON_ARRAY_SIZE(productKeysSize) + JSON_OBJECT_SIZE(7) + 50;
    DynamicJsonDocument doc(capacity);

    doc["state"] = String("offline");
    doc["infoVersionCode"] = configInfo->info_version_code;
    doc["chipId"] = String(ESP.getChipId());
    doc["deviceKey"] = configInfo->device_key;
    doc["productKeySize"] = productKeysSize;
    JsonArray productKeys = doc.createNestedArray("productKeys");
    for (int i = 0; i < productKeysSize; ++i)
    {
        productKeys.add(configInfo->product_keys[i]);
    }

    serializeJson(doc, output);

    mqttClient->setWill(reportDeviceTopic.c_str(), output.c_str(), true, 1);
}

void NetworkTask::reportDevice()
{
    if (reportDeviceTopic.isEmpty())
    {
        LOG_DEBUG("device key is empty\r\n");
        return;
    }

    String output;

    int productKeysSize = configInfo->product_key_size;

    const size_t capacity = JSON_ARRAY_SIZE(productKeysSize) + JSON_OBJECT_SIZE(7) + 50;
    DynamicJsonDocument doc(capacity);

    doc["state"] = String("online");
    doc["infoVersionCode"] = configInfo->info_version_code;
    doc["chipId"] = String(ESP.getChipId());
    doc["deviceKey"] = configInfo->device_key;
    doc["productKeySize"] = productKeysSize;
    JsonArray productKeys = doc.createNestedArray("productKeys");
    for (int i = 0; i < productKeysSize; ++i)
    {
        productKeys.add(configInfo->product_keys[i]);
    }

    serializeJson(doc, output);

    LOG_DEBUG("Send:%s -> %s\r\n", reportDeviceTopic.c_str(), output.c_str());
    mqttClient->publish(reportDeviceTopic, output, true, 1);
}

void NetworkTask::addSubscribeTopic(String topic, MQTTClientCallbackSimple cb, int qos)
{
    RecvCallback *recvCallback = new RecvCallback();
    recvCallback->topic = topic;
    recvCallback->callback = cb;
    recvCallback->qos = qos;
    _recvHandlerCallbacks.add(recvCallback);
}

void NetworkTask::removeSubscribeTopic(String topic)
{
    for (uint32_t i = 0; i < _recvHandlerCallbacks.length(); ++i)
    {
        RecvCallback *recvCallback = *_recvHandlerCallbacks.nth(i);
        if (recvCallback->topic.equals(topic))
        {
            _recvHandlerCallbacks.remove(recvCallback);
        }
    }
}

void NetworkTask::messageSend(String &topic, String &payload, bool retained, int qos)
{
    LOG_DEBUG("Send:%s -> %s\r\n", topic.c_str(), payload.c_str());
    mqttClient->publish(topic, payload, retained, qos);
}

void NetworkTask::setup()
{
    // 启动mqtt
    mqttClient->begin(configInfo->mqtt_server, String(configInfo->mqtt_port).toInt(), *wiFiClient);

    // 设置消息回调
    mqttClient->onMessage(messageReceived);

    // 设置遗言
    setWill();

    // 订阅
    if (!synchronizeDeviceTopic.isEmpty())
    {
        addSubscribeTopic(synchronizeDeviceTopic, [](String &topic, String &payload) {

            const size_t capacity = 2048;
            DynamicJsonDocument doc(capacity);

            deserializeJson(doc, payload);

            JsonObject obj = doc.as<JsonObject>();

            uint32_t info_version_code = obj["infoVersionCode"];
            if (info_version_code > configInfo->info_version_code)
            {
                //同步设备信息
                configInfo->info_version_code = info_version_code;
                //清除
                for (int i = 0; i < configInfo->product_key_size; ++i)
                {
                    strcpy(configInfo->product_keys[i], "\0");
                }
                configInfo->printConfigInfo();
                //更新
                configInfo->product_key_size = obj["productKeySize"];
                for (int i = 0; i < configInfo->product_key_size; ++i)
                {
                    if (!doc["productKeys"][i].isNull())
                    {
                        strcpy(configInfo->product_keys[i], doc["productKeys"][i]);
                    }
                }
                configInfo->printConfigInfo();

                configInfo->saveConfigFile(configInfo);

                ESP.reset();
            }
        }, 1);
    }
}

void NetworkTask::loop()
{
    if (!mqttClient->loop())
    {
        wl_status_t wlStatus = WifiUtils::wifiConnect(
                String(configInfo->sta_wifi_ssid),
                String(configInfo->sta_wifi_password),
                0);
        if (wlStatus == WL_CONNECTED)
        {
            //连接成功
            WifiUtils::printWifi();
        } else if (wlStatus == WL_CONNECT_FAILED)
        {
            //连接失败
            delay(5000);
        }

        //连接mqtt服务器
        mqttConnectSync();

        //订阅主题
        for (uint32_t i = 0; i < _recvHandlerCallbacks.length(); ++i)
        {
            RecvCallback *recvCallback = *_recvHandlerCallbacks.nth(i);
            LOG_DEBUG("subscribe topic: %s\r\n", recvCallback->topic.c_str());
            mqttClient->subscribe(recvCallback->topic, recvCallback->qos);
        }

        //上报设备信息
        reportDevice();
    }
}


namespace MqttUtils {

    MqttUtils::TopicEntity *toTopic(String topicStr)
    {
        LinkedList<String> list = StringUtils::split(topicStr, '/');
        for (uint32_t i = 0; i < list.length(); ++i)
        {
            Serial.println(*list.nth(i));
        }
        TopicEntity *topicEntity = new TopicEntity();
        return topicEntity;
    }

    String toTopicString(MqttUtils::TopicEntity *topic)
    {
        char str[sizeof(MqttUtils::TopicEntity) + 8];
        sprintf(
                str,
                "/%s/%s/%s/%s/%s",
                topic->title,
                topic->product,
                topic->device,
                topic->function,
                topic->item
        );
        return String(str);
    }

    String toTopicString(const char *title,
                         const char *product,
                         const char *device,
                         const char *function,
                         const char *item)
    {
        char str[sizeof(MqttUtils::TopicEntity) + 8];
        sprintf(
                str,
                "/%s/%s/%s/%s/%s",
                title,
                product,
                device,
                function,
                item
        );
        return String(str);
    }
}